﻿// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Xml.Linq;
using Microsoft.Xrm.Sdk.Client.Protocols.WSTrust;
using Microsoft.Xrm.Sdk.XmlNamespaces;

#endregion

namespace Microsoft.Xrm.Sdk.Client.Protocols.Policy
{
	public sealed class WSTrustPolicyImporter : ServicePolicyImporter, IPolicyImporter
	{
		#region IPolicyImporter Members

		public Boolean CanImport(XDocument document)
		{
			if (!Imported)
			{
				IEnumerable<XElement> policies = from el in document.Descendants(PublicNamespaces.WSDL + @"definitions")
												 where
													 (from de in el.Descendants(PublicNamespaces.WSP + "Policy")
													  select de).Any()
												 select el;

				return policies.Count() > 0;
			}
			return false;
		}

		public ServicePolicy ImportPolicy(XDocument document)
		{
			WSTrustPolicy wsTrustPolicy = new WSTrustPolicy();

			XElement definition = document.Element(PublicNamespaces.WSDL + @"definitions");

			IEnumerable<XElement> policies = from el in definition.Elements(PublicNamespaces.WSP + "Policy")
											 select el;
			foreach (XElement policy in policies)
			{
				TokenPolicy tokenPolicy = new TokenPolicy();

				tokenPolicy.PolicyId = policy.Attribute(PublicNamespaces.WSU + "Id").Value;

				//We will only process Trust13 endpoints.
				if (GetBindingForPolicy(document, tokenPolicy) && GetEndpointForBinding(document, tokenPolicy))
				{
					// Now, get the transport.  For us, this will be HTTPS, unless this is pure OnPremise AD, then it might be HTTP.

					ExtractTransportScheme(policy, tokenPolicy);

					IssuedTokenTemplate.ExtractToken(policy, tokenPolicy);

					BearerTokenTemplate.ExtractToken(policy, tokenPolicy);

					UserNameTokenTemplate.ExtractToken(policy, tokenPolicy);

					X509TokenTemplate.ExtractToken(policy, tokenPolicy);

					if (tokenPolicy.Tokens.Count > 0)
					{
						//Finally, if we were able to extract a token parser that we could understand, add to the list
						wsTrustPolicy.TokenPolicies[tokenPolicy.PolicyId] = tokenPolicy;
					}
				}
			}

			Imported = true;

			return wsTrustPolicy;
		}

		#endregion

		private void ExtractTransportScheme(XElement doc, TokenPolicy trustPolicy)
		{
			IEnumerable<XElement> bindingElements =
				from el in doc.Descendants(PublicNamespaces.SP + @"TransportBinding")
				select el;
			foreach (XElement bindingElement in bindingElements)
			{
				IEnumerable<XElement> transportTokens =
					from el in bindingElement.Descendants(PublicNamespaces.SP + @"TransportToken")
					select el;
				foreach (XElement transportToken in transportTokens)
				{
					XElement policy = transportToken.Element(PublicNamespaces.WSP + "Policy");
					XElement token = policy.Element(PublicNamespaces.SP + "HttpsToken");
					if (token != null)
					{
						SetValue(trustPolicy, "TransportScheme", "HTTPS");
					}
					else
					{
						token = policy.Element(PublicNamespaces.SP + "HttpToken");
						if (token != null)
						{
							SetValue(trustPolicy, "TransportScheme", "HTTP");
						}
					}
					if (token != null)
					{
						foreach (XAttribute attribute in token.Attributes())
						{
							SetValue(trustPolicy, "TOKEN:" + attribute.Name.LocalName, attribute.Value);
						}
						break;
					}
				}
			}
		}

		private static Boolean GetBindingForPolicy(XDocument doc, TokenPolicy tokenPolicy)
		{
			IEnumerable<XElement> policies = from el in doc.Descendants(PublicNamespaces.WSP + @"PolicyReference")
											 where (el.Attribute("URI").Value == "#" + tokenPolicy.PolicyId)
											 select el;

			foreach (XElement policy in policies)
			{
				String bindingName = policy.Parent.Attribute("name").Value;
				IEnumerable<XElement> operations =
					from operation in policy.Parent.Descendants(PublicNamespaces.SOAP12 + "operation")
					select operation;
				foreach (XElement operation in operations)
				{
					string action = operation.Attribute("soapAction").Value;
					//We only support 13
					if (String.Compare(action, WSTrustConstants.WSTrust13.RequestTypes.Issue) == 0)
					{
						tokenPolicy.BindingName = bindingName;
						tokenPolicy.TrustVersion = WSTrustConstants.Version.WSTrust13;
						return true;
					}
					break;
				}
			}

			return false;
		}

		private static Boolean GetEndpointForBinding(XDocument doc, TokenPolicy tokenPolicy)
		{
			IEnumerable<XElement> ports = from el in doc.Descendants(PublicNamespaces.WSDL + @"port")
										  where (el.Attribute("binding").Value.EndsWith(tokenPolicy.BindingName))
										  select el;
			foreach (XElement port in ports)
			{
				string name = port.Attribute("name").Value;
				string binding = port.Attribute("binding").Value;

				int sep = binding.IndexOf(":");
				if (sep > -1)
				{
					binding = binding.Substring(sep + 1);
				}
				if (String.Compare(tokenPolicy.BindingName, binding, StringComparison.OrdinalIgnoreCase) == 0)
				{
					XElement endpointReference = port.Element(PublicNamespaces.WSA10 + "EndpointReference");
					if (endpointReference != null)
					{
						XElement identity = endpointReference.Element(PublicNamespaces.ADDR + "Identity");
						if (identity != null)
						{
							//In Silverlight, we can not provide an identity, and we can't use integrated auth
							return false;
						}
						tokenPolicy.BindingName = name;
						XElement address = endpointReference.Element(PublicNamespaces.WSA10 + "Address");
						if (address != null)
						{
							tokenPolicy.EndpointAddress = new EndpointAddress(address.Value);
							return true;
						}
					}
				}
			}

			return false;
		}
	}


	public enum KeyTypes
	{
		None = 0,
		Symmetric,
		Asymmetric,
		Bearer,
		UserName,
		X509,
	}

	internal sealed class WSTrustPolicyManager : IPolicyImporter
	{
		private readonly ServicePolicyImporterList _policyImporters = new ServicePolicyImporterList();

		public WSTrustPolicyManager()
		{
			InitializeDefaultImporters();
		}

		public ServicePolicyImporterList PolicyImporters
		{
			get { return _policyImporters; }
		}

		#region IPolicyImporter Members

		public bool CanImport(XDocument document)
		{
			return _policyImporters.Cast<IPolicyImporter>().Any(importer => importer.CanImport(document));
		}

		public ServicePolicy ImportPolicy(XDocument document)
		{
			return (from IPolicyImporter importer in _policyImporters
					where importer.CanImport(document)
					select importer.ImportPolicy(document)).FirstOrDefault();
		}

		#endregion

		public void InitializeDefaultImporters()
		{
			_policyImporters.Add(new WSTrustPolicyImporter());
		}
	}

	public sealed class WSTrustPolicy : ServicePolicy
	{
		private readonly TokenPolicyDictionary _tokenPolicies = new TokenPolicyDictionary();

		internal TokenPolicyDictionary TokenPolicies
		{
			get { return _tokenPolicies; }
		}
	}
}
